﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Xml.Linq;
using HQPad.Helpers;
using HQPad.DAL;
using HQPad.Models;

namespace HQPad.Services
{
    public interface ICharacterImportService
    {
        CharacterImportResult ConvertHQML(IUnitOfWork context, XDocument hqmlDoc, CharacterRulesHelper helper);
    }

    public class CharacterImportService : ICharacterImportService
    {
        private int rulesId = 1;
        private string userName = string.Empty;
        private string sessionId = string.Empty;
        private ICharacterService characterService = null;

        public CharacterImportService(int rulesId, string userName, string sessionId, ICharacterService characterService)
        {
            this.rulesId = rulesId;
            this.userName = userName;
            this.sessionId = sessionId;
            this.characterService = characterService;
        }

        /// <summary>
        /// Process the hqml document passed and convert it into an hqpad xml document format.
        /// Store the document on the current users scratch pad.
        /// If the scratch pad already contains a user with the same name then make a safe name 
        /// by appending (n) to the name.
        /// As each item is processed add a message to the results object.  This is used to track 
        /// any problems with the import process.
        /// </summary>
        /// <param name="hqmlDoc"></param>
        /// <returns></returns>
        public CharacterImportResult ConvertHQML(IUnitOfWork context, XDocument hqmlDoc, CharacterRulesHelper helper)
        {
            CharacterImportResult result = new CharacterImportResult();

            XElement characterElement = hqmlDoc.Element("PlayerCharacter");

            if (characterElement == null)
            {
                result.Messages.Add(new ImportMessage() { Message = "The PlayerCharacter element is missing.  The hqml file is malformed or invalid.", IsWarning = false, IsError = true });
                result.Success = false;
                return result;
            }

            // Process the character's meta data (name etc).
            XElement nameElement = hqmlDoc.Descendants("CharacterName").FirstOrDefault();

            if (nameElement == null || string.IsNullOrEmpty(nameElement.Value))
            {
                result.Messages.Add(new ImportMessage() { Message = "The CharacterName element is missing or empty.  A default character name of 'mystery hero' will be used.", IsWarning = true, IsError = false });
                helper.SetName("mystery hero");
            }
            else
            {
                string name = nameElement.Value;

                // Check name safe for scratch pad.
                bool safeName = false;
                int count = 1;

                while (!safeName)
                {
                    string nameSlug = characterService.CreateCharacterSlug(name);

                    ScratchPad scratchCharacter = characterService.GetScratchPadCharacter(userName, sessionId, nameSlug);

                    if (scratchCharacter == null)
                        safeName = true;
                    else
                    {
                        if (count > 1)
                            name = name.Replace("(" + (count - 1).ToString() + ")", "(" + count.ToString() + ")");
                        else
                            name = name + "(" + count.ToString() + ")";

                        count++;
                    }
                }

                result.Messages.Add(new ImportMessage() { Message = string.Format("Using safe character name {0}.", name), IsWarning = false, IsError = false });
                helper.SetName(name);
            }

            // Process the characters adventure record.
            ProcessAdventureRecord(characterElement, ref result, helper);

            // Process the characters race and class from the table setting.
            ProcessRaceAndClass(characterElement, ref result, helper);

            // Process the character's guilds.
            List<Guild> guildList = ProcessGuilds(context, characterElement, ref result, helper);

            // Process the character's bought skills.
            ProcessSkills(context, characterElement, ref result, helper);

            // Process the character's spell choices and school access.
            ProcessMagic(context, characterElement, ref result, helper, guildList);

            // Process the character's invocation choices and access.
            ProcessInvocations(context, characterElement, ref result, helper);

            // Process the character's armour choice.


            // Process the character's items and abilities

            result.Success = !result.AnyError();
            return result;
        }

        #region ProcessAdventureRecord
        /// <summary>
        /// Read the adventure records int he import hqml and construct adventure records for each.
        /// The only required value is the points record.
        /// </summary>
        /// <param name="characterElement">The hqml document character element.</param>
        /// <param name="result">The import process results.</param>
        /// <param name="helper">The helper used to build the new character.</param>
        protected void ProcessAdventureRecord(XElement characterElement, ref CharacterImportResult result, CharacterRulesHelper helper)
        {
            var cells =
               (from c in characterElement.Element("AdventureRecord").Descendants("Cell")
                group c by c.Attribute("CR").Value[1] into index
                select new
                {
                    Row = index.Key,
                    Cells = (from cl in index select cl)
                });

            foreach (var groupedRecord in cells)
            {
                string description = string.Empty;
                string eventType = string.Empty;
                int? deaths = null;
                int points = 0;
                DateTime eventDate = DateTime.Now;
                AttendanceType attendanceType = AttendanceType.Unknown;
                bool recordOk = true;

                foreach (XElement cell in groupedRecord.Cells)
                {
                    switch (cell.Attribute("CR").Value[0])
                    {
                        case 'A':
                            {
                                // Extract record date.
                                if (!DateTime.TryParse(cell.Value, out eventDate))
                                    result.Messages.Add(new ImportMessage() { Message = string.Format("Adventure record on row {0} has an unrecognisable date value {1}", groupedRecord.Row, cell.Value), IsWarning = true, IsError = false });
                                break;
                            }
                        case 'B':
                            {
                                // Extract record description.
                                description = cell.Value;
                                break;
                            }
                        case 'C':
                            {
                                // Extract record event type.
                                eventType = cell.Value;
                                break;
                            }
                        case 'D':
                            {
                                // Extract record attendance type.
                                switch (cell.Value.ToLowerInvariant())
                                {
                                    case "crew":
                                    case "crewed":
                                    case "monstered": 
                                    case "monster": { attendanceType = AttendanceType.Monster; break; }
                                    case "play":
                                    case "played": 
                                    case "player": { attendanceType = AttendanceType.Player; break; }
                                    case "referee":
                                    case "ref":
                                    case "reffed":
                                    case "reff'd":
                                    case "ref'd":
                                    case "referreed": { attendanceType = AttendanceType.Referee; break; }
                                    default: { attendanceType = AttendanceType.Unknown; break; }
                                }
                                break;
                            }
                        case 'E':
                            {
                                // Extract record points.
                                if (!int.TryParse(cell.Value, out points))
                                {
                                    result.Messages.Add(new ImportMessage() { Message = string.Format("Adventure record on row {0} has an unrecognisable points value {1}, record will be ignored", groupedRecord.Row, cell.Value), IsWarning = true, IsError = false });
                                    recordOk = false;
                                }
                                break;
                            }
                        case 'F':
                            {
                                // Extract record number of deaths.
                                if (!string.IsNullOrEmpty(cell.Value))
                                {
                                    int deathsTemp = 0;
                                    if (!int.TryParse(cell.Value, out deathsTemp))
                                        result.Messages.Add(new ImportMessage() { Message = string.Format("Adventure record on row {0} has an unrecognisable points value {1}, record will be ignored", groupedRecord.Row, cell.Value), IsWarning = true, IsError = false });
                                    else
                                        deaths = deathsTemp;
                                }
                                break;
                            }
                        default: { break; }
                    }
                }

                // Add record.
                if (recordOk)
                {
                    helper.AddAdventureRecord(eventDate, description, points, deaths, attendanceType, eventType);
                    result.Messages.Add(new ImportMessage() { Message = string.Format("Added adventure record {0} for {1} points", description, points), IsWarning = false, IsError = false });
                }
            }
        }
        #endregion

        #region ProcessRaceAndClass
        /// <summary>
        /// Process the hqml points table into a race and class selections.
        /// </summary>
        /// <param name="characterElement">The hqml document character element.</param>
        /// <param name="result">The import process results.</param>
        /// <param name="helper">The helper used to build the new character.</param>
        protected void ProcessRaceAndClass(XElement characterElement, ref CharacterImportResult result, CharacterRulesHelper helper)
        {
            // Get the points table from the hqml.
            XElement tableElement = characterElement
                .Element("Header")
                .Elements("Cell")
                .Where(c => c.Attribute("CR").Value == "CharacterTable")
                .FirstOrDefault();

            if (tableElement == null || string.IsNullOrEmpty(tableElement.Value))
            {
                result.Messages.Add(new ImportMessage() { Message = "The points table value is missing.", IsWarning = false, IsError = true });
                return;
            }

            // Convert to race and class values.
            helper.SetClass(ConvertTableToClass(tableElement.Value));
            helper.SetRace(ConvertTableToRace(tableElement.Value));
        }
        #endregion

        #region ConvertTableToClass
        /// <summary>
        /// Convert an hqml table string into a points class name.
        /// </summary>
        /// <param name="table">An HQML points table value.</param>
        /// <returns>The class name for the points table.</returns>
        protected string ConvertTableToClass(string table)
        {
            string className = "Warrior";

            switch (table)
            {
                case "1":
                case "5":
                case "9": { className = "Warrior"; break; }
                case "2":
                case "6":
                case "10": { className = "Scout"; break; }
                case "3":
                case "7":
                case "11": { className = "Acolyte"; break; }
                case "4":
                case "8":
                case "112": { className = "Mage"; break; }
                default: { className = "Warrior"; break; }
            }

            return className;
        }
        #endregion

        #region ConvertTableToRace
        /// <summary>
        /// Convert an hqml table string into a points race name.
        /// </summary>
        /// <param name="table">An HQML points table value.</param>
        /// <returns>The class name for the points table.</returns>
        protected string ConvertTableToRace(string table)
        {
            string raceName = "Human";

            switch (table)
            {
                case "1":
                case "2":
                case "3":
                case "4": { raceName = "Human"; break; }
                case "5":
                case "6":
                case "7":
                case "8": { raceName = "Elf"; break; }
                case "9":
                case "10":
                case "11":
                case "12": { raceName = "Half-Orc"; break; }
                default: { raceName = "Human"; break; }
            }

            return raceName;
        }
        #endregion

        #region ProcessGuilds
        /// <summary>
        /// Process the hqml guild selections.
        /// </summary>
        /// <param name="characterElement">The hqml document character element.</param>
        /// <param name="result">The import process results.</param>
        /// <param name="helper">The helper used to build the new character.</param>
        protected List<Guild> ProcessGuilds(IUnitOfWork context, XElement characterElement, ref CharacterImportResult result, CharacterRulesHelper helper)
        {
            List<Guild> guildList = new List<Guild>();

            XElement primaryElement = characterElement
                .Element("Header")
                .Elements("Cell")
                .Where(c => c.Attribute("CR").Value == "PrimaryGuild")
                .FirstOrDefault();

            IGuildService guildService = new GuildService(context);

            if (primaryElement == null || string.IsNullOrEmpty(primaryElement.Value))
                result.Messages.Add(new ImportMessage() { Message = "No primary guild assignment found in hqml file.", IsWarning = true, IsError = false });
            else
            {
                // determine guild id.
                string primaryGuildSlug = ConvertGuildIdToSlug(primaryElement.Value);

                if (!string.IsNullOrEmpty(primaryGuildSlug))
                {
                    Guild guild = guildService.GetGuild(rulesId, primaryGuildSlug);

                    string reason = string.Empty;
                    if (!helper.SetGuild(guild, "primary", out reason))
                        result.Messages.Add(new ImportMessage() { Message = string.Format("Unable to set primary guild assignment {0}.  {1}", primaryGuildSlug, reason), IsWarning = true, IsError = false });
                    else
                    {
                        guildList.Add(guild);
                        result.Messages.Add(new ImportMessage() { Message = string.Format("Set primary guild to {0}.", primaryGuildSlug), IsWarning = false, IsError = false });

                        // try secondary guild.
                        XElement secondaryElement = characterElement
                           .Element("Header")
                           .Elements("Cell")
                           .Where(c => c.Attribute("CR").Value == "SecondaryGuild")
                           .FirstOrDefault();

                        if (secondaryElement != null && string.IsNullOrEmpty(secondaryElement.Value))
                        {
                            string secondaryGuildSlug = ConvertGuildIdToSlug(secondaryElement.Value);
                            if (!string.IsNullOrEmpty(secondaryGuildSlug))
                            {
                                Guild secondaryGuild = guildService.GetGuild(rulesId, secondaryGuildSlug);

                                if (!helper.SetGuild(secondaryGuild, "secondary", out reason))
                                    result.Messages.Add(new ImportMessage() { Message = string.Format("Unable to set secondary guild assignment {0}.  {1}", secondaryGuildSlug, reason), IsWarning = true, IsError = false });
                                else
                                {
                                    guildList.Add(secondaryGuild);
                                    result.Messages.Add(new ImportMessage() { Message = string.Format("Set secondary guild to {0}.", secondaryGuildSlug), IsWarning = false, IsError = false });
                                    
                                    // try tertiary guild.
                                    XElement tertiaryElement = characterElement
                                       .Element("Header")
                                       .Elements("Cell")
                                       .Where(c => c.Attribute("CR").Value == "TertiaryGuild")
                                       .FirstOrDefault();

                                    if (tertiaryElement != null && string.IsNullOrEmpty(tertiaryElement.Value))
                                    {
                                        string tertiaryGuildSlug = ConvertGuildIdToSlug(tertiaryElement.Value);
                                        if (!string.IsNullOrEmpty(tertiaryGuildSlug))
                                        {
                                            Guild tertiaryGuild = guildService.GetGuild(rulesId, tertiaryGuildSlug);

                                            if (!helper.SetGuild(tertiaryGuild, "tertiary", out reason))
                                                result.Messages.Add(new ImportMessage() { Message = string.Format("Unable to set tertiary guild assignment {0}.  {1}", tertiaryGuildSlug, reason), IsWarning = true, IsError = false });
                                            else
                                            {
                                                guildList.Add(tertiaryGuild);
                                                result.Messages.Add(new ImportMessage() { Message = string.Format("Set tertiary guild to {0}.", tertiaryGuildSlug), IsWarning = false, IsError = false });
                                            }
                                        }
                                    }
                                
                                }
                            }
                       }
                    }
                }
            }

            return guildList;
        }
        #endregion

        #region ConvertGuildIdToSlug
        /// <summary>
        /// Convert the guild id number from an hqml document into a guild slug string.
        /// </summary>
        /// <param name="guild">An hqml guild id.</param>
        /// <returns>An hqpad guild slug.</returns>
        protected string ConvertGuildIdToSlug(string guild)
        {
            string guildSlug = "";

            switch (guild)
            {
                case "1": { guildSlug = "archers"; break; }
                case "2": { guildSlug = "the-wolfhold-scouts"; break; }
                case "3": { guildSlug = "barbarians"; break; }
                case "4": { guildSlug = "black-school-of-magic"; break; }
                case "5": { guildSlug = "blue-school-of-magic"; break; }
                case "6": { guildSlug = "temple-of-earth-magic"; break; }
                case "7": { guildSlug = "crusaders"; break; }
                case "8": { guildSlug = "dark-brethren"; break; }
                case "9": { guildSlug = "dark-path"; break; }
                case "10": { guildSlug = "dark-seers"; break; }
                case "11": { guildSlug = "druids"; break; }
                case "12": { guildSlug = "fell-knights-of-ushaz"; break; }
                case "13": { guildSlug = "green-school-of-magic"; break; }
                case "14": { guildSlug = "grey-gauntlet"; break; }
                case "15": { guildSlug = "grey-path"; break; }
                case "16": { guildSlug = "grey-school-of-magic"; break; }
                case "17": { guildSlug = "grey-wardens"; break; }
                case "18": { guildSlug = "hospitallers"; break; }
                case "19": { guildSlug = "humacti"; break; }
                case "20": { guildSlug = "the-iron-guard"; break; }
                case "21": { guildSlug = "the-knights-martial"; break; }
                case "22": { guildSlug = "michelines"; break; }
                case "23": { guildSlug = "the-monastery"; break; }
                case "24": { guildSlug = "necromancers"; break; }
                case "25": { guildSlug = "order-of-saint-michel"; break; }
                case "26": { guildSlug = "the-pathfinders"; break; }
                case "27": { guildSlug = "the-rangers"; break; }
                case "28": { guildSlug = "reapers"; break; }
                case "29": { guildSlug = "red-school-of-magic"; break; }
                case "30": { guildSlug = "the-seekers"; break; }
                case "31": { guildSlug = "white-path"; break; }
                case "32": { guildSlug = "white-school-of-magic"; break; }
                case "33": { guildSlug = "yellow-guild"; break; }
                default: { break; }
            }

            return guildSlug;
        }
        #endregion

        #region ProcessSkills
        /// <summary>
        /// Process the hqml skill selections.
        /// </summary>
        /// <param name="context">Database context.</param>
        /// <param name="characterElement">The hqml document character element.</param>
        /// <param name="result">The import process results.</param>
        /// <param name="helper">The helper used to build the new character.</param>
        protected void ProcessSkills(IUnitOfWork context, XElement characterElement, ref CharacterImportResult result, CharacterRulesHelper helper)
        {
            ISkillService skillService = new SkillService(context);

            IEnumerable<XElement> skills = characterElement
                .Element("CharacterSkillNames")
                .Elements("Cell");

            foreach (XElement skill in skills)
            {
                if (skill.Attribute("CellText") == null || string.IsNullOrEmpty(skill.Attribute("CellText").Value))
                {
                    result.Messages.Add(new ImportMessage() { Message = string.Format("Invalid or missing legacy skill name from row: {0}", skill.ToString()), IsWarning = true, IsError = false });
                    continue;
                }

                string legacyName = skill.Attribute("CellText").Value;

                int ranks = 0;

                if (string.IsNullOrEmpty(skill.Value) || !int.TryParse(skill.Value, out ranks))
                {
                    result.Messages.Add(new ImportMessage() { Message = string.Format("Invalid or missing skill ranks from row: {0}", skill.ToString()), IsWarning = true, IsError = false });
                    continue;
                }

                // Get skill slug from legacy name.
                string skillSlug = skillService.GetSkillSlugByLegacyName(rulesId, legacyName);

                // Add bought skill to character.
                string reason = string.Empty;

                if (!helper.UpdateSkill(skillSlug, ranks, out reason))
                {
                    result.Messages.Add(new ImportMessage() { Message = string.Format("Unable to add skill {1} from row: {0}", skill.ToString(), skillSlug), IsWarning = true, IsError = false });
                }
                else
                    result.Messages.Add(new ImportMessage() { Message = string.Format("Added skill {1} with {0} ranks", ranks, skillSlug), IsWarning = false, IsError = false });
            }
        }
        #endregion

        #region ProcessMagic
        /// <summary>
        /// Process the import file to determine school access and spell choices.
        /// </summary>
        /// <param name="context"></param>
        /// <param name="characterElement"></param>
        /// <param name="result"></param>
        /// <param name="helper"></param>
        protected void ProcessMagic(IUnitOfWork context, XElement characterElement, ref CharacterImportResult result, CharacterRulesHelper helper, List<Guild> guildList)
        {
            IGuildService guildService = new GuildService(context);
            ISpellService spellService = new SpellService(context);

            IEnumerable<XElement> accessCells = characterElement
                .Element("MagicInformation")
                .Elements("Cell");

            List<string> extraColourSkills = new List<string>() {
                "extra-colour-4th",
                "extra-colour-5th",
                "extra-colour-6th",
                "extra-colour-7th",
                "extra-colour-8th"};

            #region school access options
            Hashtable access = new Hashtable();

            // Gather all the selected school access levels from the import file.
            foreach (XElement accessElement in accessCells)
            {
                if (accessElement.Attribute("CR") == null || string.IsNullOrEmpty(accessElement.Attribute("CR").Value))
                {
                    result.Messages.Add(new ImportMessage() { Message = string.Format("Invalid or missing school access name from row"), IsWarning = true, IsError = false });
                    continue;
                }

                string school = accessElement.Attribute("CR").Value;
                string level = accessElement.Value;

                if (level == "1" || school == "cellShowSpells")
                    continue; // ignore entries that say 'none' or the flag to show/hide all spells.

                string title = "Mage";
                string colour = "Grey";

                // determine Wizard or Sorceror level and colour.
                if (school.Contains("Wizard"))
                {
                    title = (level == "2") ? "Mage" : "Wizard";
                    colour = school.Substring(0, school.IndexOf("Wizard"));

                    if (colour == "Gray") colour = "Grey";
                }
                else if (school.Contains("Sorceror"))
                {
                    title = "Sorceror";
                    colour = school.Substring(0, school.IndexOf("Sorceror"));
                    if (colour == "Gray") colour = "Grey";
                }

                if (access.ContainsKey(access))
                {
                    if (title == "Sorceror")
                        access[colour] = title;
                    if (title == "Wizard" && access[colour].ToString() == "Mage")
                        access[colour] = "Wizard";
                }
                else
                    access.Add(colour, title);
            }

            // Now get the existing (unfilled) school access for the character so we can match up the two.
            List<SchoolAccessModelItem> accessItems = helper.GetSchoolAccessOptions();

            // Attempt primary guild first.
            Guild primaryGuild = guildList.Count > 0 ? guildList[0] : null;

            if (primaryGuild != null && primaryGuild.PrimaryClass == "Mage")
            {
                // Get imported access level for school.
                string level = access[primaryGuild.MagicColour].ToString();

                // primary guild has school-1 = mage, make-focus = wizard and sorceror-in-primary-colour = sorceror access.

                SchoolAccessModelItem school1Item = accessItems.Where(a => a.AccessSlug == "school-1").FirstOrDefault();

                if (school1Item != null)
                    school1Item.SchoolSlug = primaryGuild.Slug;

                if (level == "Wizard" || level == "Sorceror")
                {
                    SchoolAccessModelItem makeFocusItem = accessItems.Where(a => a.AccessSlug == "make-focus").FirstOrDefault();

                    if (makeFocusItem != null)
                        makeFocusItem.SchoolSlug = primaryGuild.Slug;
                }

                if (level == "Sorceror")
                {
                    SchoolAccessModelItem primarySorcerorItem = accessItems.Where(a => a.AccessSlug == "sorceror-in-primary-colour").FirstOrDefault();

                    if (primarySorcerorItem != null)
                        primarySorcerorItem.SchoolSlug = primaryGuild.Slug;
                }

                access.Remove(primaryGuild.MagicColour);

                // Now attempt secondary guild.
                Guild secondaryGuild = guildList.Count > 1 ? guildList[1] : null;

                if (secondaryGuild != null && secondaryGuild.PrimaryClass == "Mage")
                {
                    level = access[secondaryGuild.MagicColour].ToString();
                    // secondary guild has school-2 = mage, wizard-in-2nd-colour = wizard and sorceror-in-2nd-colour = sorceror access.

                    SchoolAccessModelItem school2Item = accessItems.Where(a => a.AccessSlug == "school-2").FirstOrDefault();

                    if (school2Item != null)
                        school2Item.SchoolSlug = secondaryGuild.Slug;

                    if (level == "Wizard" || level == "Sorceror")
                    {
                        SchoolAccessModelItem wizardItem = accessItems.Where(a => a.AccessSlug == "wizard-in-2nd-colour").FirstOrDefault();

                        if (wizardItem != null)
                            wizardItem.SchoolSlug = secondaryGuild.Slug;
                    }

                    if (level == "Sorceror")
                    {
                        SchoolAccessModelItem secondarySorcerorItem = accessItems.Where(a => a.AccessSlug == "sorceror-in-2nd-colour").FirstOrDefault();

                        if (secondarySorcerorItem != null)
                            secondarySorcerorItem.SchoolSlug = secondaryGuild.Slug;
                    }

                    access.Remove(secondaryGuild.MagicColour);


                    // Now attempt tertiary guild.
                    Guild tertiaryGuild = guildList.Count > 2 ? guildList[2] : null;

                    if (tertiaryGuild != null && tertiaryGuild.PrimaryClass == "Mage")
                    {
                        level = access[tertiaryGuild.MagicColour].ToString();
                        // tertiary guild has extra-colour-3rd = mage and wizard-in-3rd-colour = wizard access.

                        SchoolAccessModelItem school3Item = accessItems.Where(a => a.AccessSlug == "extra-colour-3rd").FirstOrDefault();

                        if (school3Item != null)
                            school3Item.SchoolSlug = tertiaryGuild.Slug;

                        if (level == "Wizard" || level == "Sorceror")
                        {
                            SchoolAccessModelItem wizardItem = accessItems.Where(a => a.AccessSlug == "wizard-in-3rd-colour").FirstOrDefault();

                            if (wizardItem != null)
                                wizardItem.SchoolSlug = tertiaryGuild.Slug;
                        }

                        access.Remove(tertiaryGuild.MagicColour);
                    }
                    else // no tertiaty guild.
                    {
                        extraColourSkills.Insert(0, "extra-colour-3rd");
                    }
                }
                else // no secondary guild.
                {
                    extraColourSkills.Insert(0, "extra-colour-3rd");
                    extraColourSkills.Insert(0, "school-2");
                }
            }

            // Now process any other access items into the extra colours available.
            
            foreach (string colour in access.Keys)
            {
                string level = access[colour].ToString();

                // pop next skill
                string skill = extraColourSkills.FirstOrDefault();

                if (string.IsNullOrEmpty(skill))
                    break;

                // get access item with no school.
                SchoolAccessModelItem item = accessItems.Where(a => string.IsNullOrEmpty(a.SchoolSlug) && a.AccessSlug == skill).FirstOrDefault();

                if (item == null)
                    break;

                // get guild slug for colour.
                Guild colourGuild = guildService.GetGuilds(rulesId, "Mage").Where(a => !string.IsNullOrEmpty(a.MagicColour) && a.MagicColour == colour).FirstOrDefault();

                if (colourGuild == null)
                    continue;

                item.SchoolSlug = colourGuild.Slug;
                extraColourSkills.RemoveAt(0);
            }

            helper.SaveSchoolOptions(accessItems); // save back, filled access options.
            #endregion

            #region spell choices

            XElement spellsElement = characterElement.Element("SpellNames");

            if (spellsElement == null)
                return; // no spells.

            IEnumerable<XElement> spellCells = spellsElement.Elements("Cell");

            if (spellCells == null)
                return; // no spells.

            foreach (XElement cell in spellCells)
            {
                // get spell name.
                if (cell.Attribute("CellText") == null || string.IsNullOrEmpty(cell.Attribute("CellText").Value))
                {
                    result.Messages.Add(new ImportMessage() { Message = string.Format("Invalid or missing legacy spell name."), IsWarning = true, IsError = false });
                    continue;
                }

                string legacyName = cell.Attribute("CellText").Value;
                string level = cell.Value;

                bool selected = level == "1" || level == "2";
                bool attuned = level == "2";

                // get spell group name from the legacy name provided.
                Spell spell = spellService.GetSpellByLegacy(rulesId, legacyName);

                if (spell != null)
                {
                    // Add selected spell.
                    helper.UpdateSpell(spell.GroupSlug, selected, attuned);
                    result.Messages.Add(new ImportMessage() { Message = string.Format("Added selected spell {0}", spell.GroupName), IsError = false, IsWarning = false });
                }
            }

            #endregion
        }
        #endregion

        protected void ProcessInvocations(IUnitOfWork context, XElement characterElement, ref CharacterImportResult result, CharacterRulesHelper helper)
        {
            // Find all *InvocationNames elements under the PlayerCharacter element.
            List<XElement> invocationCells = characterElement.Elements()
                .Where(a => a.Name.LocalName.ToLower().EndsWith("invocationnames"))
                .SelectMany(b => b.Elements("Cell")).ToList();

            if (invocationCells == null || invocationCells.Count == 0)
            {
                result.Messages.Add(new ImportMessage() { Message = "There are no known invocations to process.", IsError = false, IsWarning = true });
                return;
            }

            IInvocationService invocationService = new InvocationService(context);

            foreach (XElement cell in invocationCells)
            {
                // Get the cell text (legacy name).
                XAttribute legacyNameAttribute = cell.Attributes("CellText").FirstOrDefault();

                if (legacyNameAttribute == null || string.IsNullOrEmpty(legacyNameAttribute.Value))
                {
                    result.Messages.Add(new ImportMessage() { Message = "Known invocations is missing legacy name attribute 'CellText'.", IsError = false, IsWarning = true });
                    continue;
                }

                string legacyName = legacyNameAttribute.Value;

                // Get the new invocation for this invocation.
                Invocation invocation = invocationService.GetInvocationByLegacy(legacyName, rulesId);

                if (invocation == null)
                {
                    result.Messages.Add(new ImportMessage() { Message = string.Format("The legacy invocation name {0} could not be translated to a new invocation record.", legacyName), IsError = false, IsWarning = true });
                    continue;
                }

                helper.UpdateInvocation(invocation.GroupSlug, true);
                result.Messages.Add(new ImportMessage() { Message = string.Format("Add invocation {0}, rank {1}.", invocation.Name, invocation.Rank), IsError = false, IsWarning = false });
            }
        }
    }
}